﻿"use strict";

function GameObject(args)
{
    Copy(this, args);
}
GameObject.prototype._class = 'GameObject';
GameObject.prototype.id = 'GameObject_ID';
GameObject.prototype.name = 'GameObject_Name';
GameObject.prototype.Save = function ()
{
    return Save(this);
};
GameObject.prototype.Load = function (objid)
{
    var obj = Load(objid);
    if (obj != null)
    {
        for (var key in obj)
            this[key] = obj[key];
    }
}

function TileMap(args)
{
    Copy(this, args);
    this.BuildWalkMap();
    this.adj = Math.random() * 1000;
    this.colors = this.GetColors();
    for (var i = 0; i < this.colors.length; i++)
    {
        log(i + ' - ' + this.colors[i].color);
    }
}
TileMap.prototype = new GameObject();
TileMap.prototype._class = 'TileMap';
TileMap.prototype.id = null;
TileMap.prototype.name = null;
TileMap.prototype.showregion = false;
TileMap.prototype.showblocks = false;
TileMap.prototype.tilesize = 32;
TileMap.prototype.backcolor = "#FFEEDD";
TileMap.prototype.adj = .1;
TileMap.prototype.cache = {};
TileMap.prototype.roundcorners = true;
TileMap.prototype.blocksize = 10;
TileMap.prototype.edges = null;
TileMap.prototype.Save = function ()
{
    GameObject.prototype.Save.call(this);
    log('Width: ' + this.Width() + ' Height: ' + this.Height());
}
TileMap.prototype.Load = function (objid)
{
    GameObject.prototype.Load.call(this, objid);
    log('Width: ' + this.Width() + ' Height: ' + this.Height());
}
TileMap.prototype.Randomize = function (size)
{
    var w = this.tilemap[0].length;
    var h = this.tilemap.length;
    for (var yy = 0; yy < h; yy++)
    {
        for (var xx = 0; xx < w; xx++)
        {
            var n = this.PerlinNoise(size * xx / w, size * yy / h, this.adj);
            var value = Math.floor(6 * n);
            this.tilemap[yy][xx] = ['0', value * 32];
        }
    }
    this.adj += .1;
    this.BuildWalkMap();
}
TileMap.prototype.SetSize = function (width, height)
{
    this.tilemap = [];
    for (var y = 0; y < height; y++)
    {
        var row = [];
        for (var x = 0; x < height; x++)
        {
            row.push(['0', 96]);
        }
        this.tilemap.push(row);
    }
    this.BuildWalkMap();
    log('Set size of map to ' + width + ',' + height);
};
TileMap.prototype.Set = function (property, value)
{
    this[property] = value;
    this.Refresh();
}
TileMap.prototype.PlaceTile = function (x, y, tilenum)
{
    this.tilemap[y][x] = tilenum;
    this.BuildWalkMap();
};
TileMap.prototype.Intersects = function (p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y)
{
    var retval = (p2y < p3y || p1y > p4y || p2x < p3x || p1x > p4x);
    return !retval;
};
TileMap.prototype.Refresh = function ()
{
    this.cache = {};
}
TileMap.prototype.GetTile = function (cx, cy)
{
    var tilesize = engine.tilesize;
    var canvasW = engine.canvas.width;
    var canvasH = engine.canvas.height;

    // Figure out which block the camera is in
    var regionx = Math.floor(cx / this.blocksize);
    var regiony = Math.floor(cy / this.blocksize);
    var key = 'y' + regiony + '_x' + regionx + '  r' + (this.showregion ? '1' : '0');
    var img = null;

    // Return the graphic if possible
    try
    {
        img = this.cache[key];
    } catch (ex)
    {
        img = null;
    }
    if (img)
        return img;

    // Create the block
    //log('Creating block ' + cx + ',' + cy);
    var tile = document.createElement('canvas');
    tile.width = this.blocksize * tilesize;
    tile.height = this.blocksize * tilesize;

    // Draw the block
    var ctx = tile.getContext('2d');
    ctx.strokeStyle = 'rgba(0, 0, 0, .5)';

    var imagesloaded = true;

    for (var adjy = 0; adjy < this.blocksize; adjy++)
    {
        var y = regiony * this.blocksize + adjy;
        if (y >= this.tilemap.length)
            continue;
        for (var adjx = 0; adjx < this.blocksize ; adjx++)
        {
            var x = regionx * this.blocksize + adjx;
            if (x >= this.tilemap[0].length)
                continue;

            var index = this.tilemap[y][x];
            var filename = 'data/Graphics/Sarah/' + index[0] + '.png';
            img = library.LoadGraphic(filename);
            if (img == null || !tilesize)
            {
                imagesloaded = false;
            } else
            {
                var srctileswidth = Math.floor(img.w / tilesize);
                var srcx = index[1] % srctileswidth;
                var srcy = Math.floor(index[1] / srctileswidth);
                var dstx = (x - regionx * this.blocksize) * tilesize;
                var dsty = (y - regiony * this.blocksize) * tilesize;

                // Draw base image
                ctx.drawImage(img,
                   srcx * tilesize, srcy * tilesize, tilesize, tilesize,
                   dstx, dsty, tilesize, tilesize);

                // Round the corners.
                if (this.roundcorners)
                {
                    var d = this.edges[y][x];
                    for (var i = 0; i < d.decorations.length; i++)
                    {
                        if (true)
                        {
                            var srcx = d.decorations[i] % srctileswidth;
                            var srcy = Math.floor(d.decorations[i] / srctileswidth);
                            ctx.drawImage(img,
                               srcx * tilesize, srcy * tilesize, tilesize, tilesize,
                               dstx, dsty, tilesize, tilesize);
                        }
                    }
                }

                // Draw region
                if (this.showregion)
                {
                    var colornum = this.regionmap[y][x] % this.colors.length;
                    var color = this.colors[colornum];
                    ctx.drawImage(color.img, dstx, dsty);
                    font.drawText(UI.prototype.fontname, ctx, dstx, dsty, this.regionmap[y][x], "#FFFFFF");
                }

                // Draw grid
                if (this.showgrid)
                {
                    ctx.strokeRect(dstx, dsty, tilesize, tilesize);

                }
            }
        }
    }

    if (this.showblocks)
    {
        font.drawText(UI.prototype.fontname, ctx, 10, 10, key + ' at ' + engine.now, "#FFFFFF");
        ctx.strokeStyle = '#000000';
        ctx.strokeRect(0, 0, this.blocksize * engine.tilesize, this.blocksize * engine.tilesize);
    }

    // Cache the block
    if (imagesloaded)
    {
        this.cache[key] = tile;
    }

    // Return the block
    return tile;
}
TileMap.prototype.Draw = function (ctx, camera)
{
    // Tile position of camera
    var cx = Math.floor(engine.camera.x / engine.tilesize);
    var cy = Math.floor(engine.camera.y / engine.tilesize);

    // This tile is top left
    var topleftpixels = {
        x: (engine.camera.x).clamp(0, this.Width() * engine.tilesize - 1),
        y: (engine.camera.y).clamp(0, this.Height() * engine.tilesize - 1)
    };
    var blockpixels = this.blocksize * engine.tilesize;
    var p1x = topleftpixels.x, p1y = topleftpixels.y, p2x = topleftpixels.x + engine.canvas.width, p2y = topleftpixels.y + engine.canvas.height;
    var p3x = 0, p3y = 0, p4x = 0, p4y = 0;
    for (var y = 0; y < this.tilemap.length * engine.tilesize; y += blockpixels)
    {
        for (var x = 0; x < this.tilemap[0].length * engine.tilesize; x += blockpixels)
        {
            p3x = x, p3y = y, p4x = x + blockpixels, p4y = y + blockpixels;
            if (!(p2y < p3y || p1y > p4y || p2x < p3x || p1x > p4x))
            {
                var cx = Math.floor(x / engine.tilesize);
                var cy = Math.floor(y / engine.tilesize);
                var tile = this.GetTile(cx, cy);
                if (tile)
                {
                    ctx.drawImage(tile, x - engine.camera.x, y - engine.camera.y);
                }
            }
        }
    }
}
TileMap.prototype.Width = function ()
{
    return this.tilemap[0].length;
};
TileMap.prototype.Height = function ()
{
    return this.tilemap.length;
};
TileMap.prototype.BuildWalkMap = function ()
{
    // Build walk map
    this.cache = {};
    this.walkmap = [];
    for (var x = 0; x < this.tilemap[0].length; x++)
    {
        var row = [];
        for (var y = 0; y < this.tilemap.length; y++)
        {
            if (this.tilemap[y][x][1] == 0 || this.tilemap[y][x][1] == 128)
                row.push(1);
            else
                row.push(0);
        }
        this.walkmap.push(row);
    }
    this.BuildRegionMap();
    this.CalculateEdges();
}
TileMap.prototype.IsWalkable = function (x, y, fillcolor)
{
    if (this.walkmap == null)
        this.BuildWalkMap();
    if (x < 0 ||
        y < 0 ||
        x >= this.tilemap[0].length ||
        y >= this.tilemap.length)
        return false;
    if (fillcolor != null && this.RegionColor(x, y) != fillcolor)
        return false;
    if (this.walkmap[x][y] == 1)
        return false;
    else
        return true;
}
TileMap.prototype.RegionColor = function (x, y)
{
    return this.regionmap[y][x];
}
TileMap.prototype.GetBuffer = function (x, y, w, h)
{

}
TileMap.prototype.BuildRegionMap = function ()
{
    this.regionmap = [];
    var ctr = 1;

    // Create an empty region map
    for (var y = 0; y < this.tilemap.length; y++)
    {
        var row = [];
        for (var x = 0; x < this.tilemap[0].length; x++)
        {
            row.push(0);
        }
        this.regionmap.push(row);
    }

    for (var x = 0; x < this.tilemap[0].length; x++)
    {
        for (var y = 0; y < this.tilemap.length; y++)
        {
            if (this.regionmap[y][x] == 0)
            {
                if (this.IsWalkable(x, y))
                {
                    this.FloodFillRegion(x, y, ctr++);
                }
            }
        }
    }
    try
    {
        log('TileMap: ' + ctr + ' regions identified.');
    } catch (ex)
    {
        // don't care
    }
    return true;
};
TileMap.prototype.CalculateEdges = function ()
{
    log('CalculateEdges');
    // Create an array to hold all the edge pieces
    var arr = [];
    var ctr = 0;
    var w = 0, n = 0, e = 0, s = 0, nw = 0, ne = 0, se = 0, sw = 0;
    var flags, flag, color, border, dots, d, x, y, me, num;
    var lookup = {
        '0000': 0,
        '1000': 1,
        '0100': 2,
        '1100': 3,
        '0010': 4,
        '1010': 5,
        '0110': 6,
        '1110': 7,
        '0001': 8,
        '1001': 9,
        '0101': 10,
        '1101': 11,
        '0011': 12,
        '1011': 13,
        '0111': 14,
        '1111': 15
    };

    var maxdecs = 0;
    for (var y = 0; y < this.tilemap.length; y++)
    {
        var row = [];
        for (x = 0; x < this.tilemap[0].length; x++)
        {
            w = 0; n = 0; e = 0; s = 0; nw = 0; ne = 0; se = 0; sw = 0;
            d = { decorations: [] };
            row.push(d);
            me = this.tilemap[y][x][1];
            if (x > 0 && this.tilemap[y][x - 1][1] > me) w = 1;
            if (y > 0 && this.tilemap[y - 1][x][1] > me) n = 1;
            if (x < this.tilemap[0].length - 1 && this.tilemap[y][x + 1][1] > me) e = 1;
            if (y < this.tilemap.length - 1 && this.tilemap[y + 1][x][1] > me) s = 1;

            if (x > 0 && y > 0 && this.tilemap[y - 1][x - 1][1] > me) nw = 1;
            if (x < this.tilemap[0].length - 1 && y > 0 && this.tilemap[y - 1][x + 1][1] > me) ne = 1;
            if (x < this.tilemap[0].length - 1 && y < this.tilemap.length - 1 && this.tilemap[y + 1][x + 1][1] > me) se = 1;
            if (x > 0 && y < this.tilemap.length - 1 && this.tilemap[y + 1][x - 1][1] > me) sw = 1;

            border = [w, n, e, s];
            dots = [nw, ne, se, sw];


            for (num = 0; num < 6; num++)
            {
                w = 0; n = 0; e = 0; s = 0; nw = 0; ne = 0; se = 0; sw = 0;
                color = num * 32;
                if (x > 0 && this.tilemap[y][x - 1][1] == color) w = 1;
                if (y > 0 && this.tilemap[y - 1][x][1] == color) n = 1;
                if (x < this.tilemap[0].length - 1 && this.tilemap[y][x + 1][1] == color) e = 1;
                if (y < this.tilemap.length - 1 && this.tilemap[y + 1][x][1] == color) s = 1;

                if (x > 0 && y > 0 &&
                    this.tilemap[y - 1][x - 1][1] == color && this.tilemap[y - 1][x][1] != color && this.tilemap[y][x - 1][1] != color) nw = 1;
                if (x < this.tilemap[0].length - 1 && y > 0
                    && this.tilemap[y - 1][x + 1][1] == color && this.tilemap[y - 1][x][1] != color && this.tilemap[y][x + 1][1] != color) ne = 1;
                if (x < this.tilemap[0].length - 1 && y < this.tilemap.length - 1 &&
                    this.tilemap[y + 1][x + 1][1] == color && this.tilemap[y + 1][x][1] != color && this.tilemap[y][x + 1][1] != color) se = 1;
                if (x > 0 && y < this.tilemap.length - 1 &&
                this.tilemap[y + 1][x - 1][1] == color && this.tilemap[y + 1][x][1] != color && this.tilemap[y][x - 1][1] != color) sw = 1;

                // Draw dots
                var dotflags = [nw, ne, se, sw];
                var flag2 =
                    ((dots[0] & dotflags[0]) ? '1' : '0') +
                    ((dots[1] & dotflags[1]) ? '1' : '0') +
                    ((dots[2] & dotflags[2]) ? '1' : '0') +
                    ((dots[3] & dotflags[3]) ? '1' : '0');
                if (flag2 != '0000')
                {
                    if (this.tilemap[y][x][0] == '0')
                    d.decorations.push(color + 16 + lookup[flag2]);
                    if (d.decorations.length > maxdecs)
                        maxdecs = d.decorations.length;
                    ctr += 1;
                }

                // Draw border
                flags = [w, n, e, s];
                flag = ((border[0] & flags[0]) ? '1' : '0') +
                        ((border[1] & flags[1]) ? '1' : '0') +
                        ((border[2] & flags[2]) ? '1' : '0') +
                        ((border[3] & flags[3]) ? '1' : '0');
                if (flag != '0000')
                {
                    if (this.tilemap[y][x][0] == '0')
                        d.decorations.push(color + lookup[flag]);
                    ctr += 1;
                }

            }
        }
        arr.push(row);
    }

    log(ctr + ' edge tiles created. Max decorations: ' + maxdecs);
    this.edges = arr;

}
TileMap.prototype.GetColors = function (num)
{
    var alpha = '.5';
    var colors = [
        { color: 'rgba(  0,   0,   0, ' + alpha + ')', img: null },
        { color: 'rgba(  0, 255,   0, ' + alpha + ')', img: null },
        { color: 'rgba(255,   0,   0, ' + alpha + ')', img: null },
        { color: 'rgba(  0,   0, 255, ' + alpha + ')', img: null },
        { color: 'rgba(255,   0, 255, ' + alpha + ')', img: null },
        { color: 'rgba(255, 255,   0, ' + alpha + ')', img: null },
        { color: 'rgba(  0, 255, 255, ' + alpha + ')', img: null },
        { color: 'rgba(255, 255, 255, ' + alpha + ')', img: null }];
    for (var i = 0; i < colors.length; i++)
    {
        colors[i].img = CreateCanvas(this.tilesize, this.tilesize, colors[i].color);
    }
    return colors;
}
TileMap.prototype.FloodFillRegion = function (x, y, regionid)
{
    if (regionid == 0)
        throw 'You must use 1 or higher, as the map expects 0 to mean that spot is empty!';
    var toFill = [[x, y]];
    var maxw = this.regionmap[0].length;
    var maxh = this.regionmap.length;
    while (toFill.length > 0)
    {
        var xy = toFill.pop();
        var x = xy[0];
        var y = xy[1];
        if (y < 0 || y >= maxh)
        {
            // skip
        }
        else if (x < 0 || x >= maxw)
        {
            //skip
        }
        else if (this.IsWalkable(x, y) && this.regionmap[y][x] == 0)
        {
            this.regionmap[y][x] = regionid;
            toFill.push([x - 1, y]);
            toFill.push([x + 1, y]);
            toFill.push([x, y - 1]);
            toFill.push([x, y + 1]);
        }
    }
}
TileMap.prototype.PerlinNoise = function (x, y, z)
{
    var LOG_POINTFIVE = -0.6931471805599453;
    return noise(x, y, z);
    function gain(a, b)
    {

        var p = Math.log(1. - b) / LOG_POINTFIVE;

        if (a < .001)
            return 0.;
        if (a > .999)
            return 1.;
        if (a < 0.5)
            return Math.pow(2 * a, p) / 2;
        var retval = 1. - Math.pow(2 * (1. - a), p) / 2;
        return retval;
    }
    function noise(x, y, z)
    {

        var p = new Array(512)
        var permutation = [151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180];
        for (var i = 0; i < 256 ; i++)
            p[256 + i] = p[i] = permutation[i];

        var X = Math.floor(x) & 255,
            Y = Math.floor(y) & 255,
            Z = Math.floor(z) & 255;
        x -= Math.floor(x);
        y -= Math.floor(y);
        z -= Math.floor(z);
        var u = fade(x),
               v = fade(y),
               w = fade(z);
        var A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z,
            B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z;

        var retval = scale(lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z),
                                       grad(p[BA], x - 1, y, z)),
                               lerp(u, grad(p[AB], x, y - 1, z),
                                       grad(p[BB], x - 1, y - 1, z))),
                       lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1),
                                       grad(p[BA + 1], x - 1, y, z - 1)),
                               lerp(u, grad(p[AB + 1], x, y - 1, z - 1),
                                       grad(p[BB + 1], x - 1, y - 1, z - 1)))));
        return gain(retval, .6);
    }
    function fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
    function lerp(t, a, b) { return a + t * (b - a); }
    function grad(hash, x, y, z)
    {
        var h = hash & 15;
        var u = h < 8 ? x : y,
               v = h < 4 ? y : h == 12 || h == 14 ? x : z;
        return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
    }
    function scale(n) { return (1 + n) / 2; }
}

